/*
 * This file is part of DALS - Distributed Artificial Life Simulation.
 * DALS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Foobar is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with DALS.  If not, see <http://www.gnu.org/licenses/>.
 */
/*
 * MapaView.java
 *
 * Created on 26/08/2010, 11:03:03
 */
package genetic.view;

import genetic.Environment;
import genetic.components.Individual;
import genetic.components.Represetative2dObject;
import genetic.message.MoveMessage;
import genetic.util.DisplayImage;
import genetic.util.ImageCache;
import genetic.util.ObservableProperty;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.util.Observable;
import java.util.Observer;

/**
 * Panel that display the environment, individuals, foods, etc
 * this panel can be added to a jFrame
 *
 * @todo when an Individual dies and is selected (selectedObjectFollow) it does
 * not disappear from map
 *
 * @author romulo
 */
public class MapaView extends javax.swing.JPanel implements Observer {

    private int size = 10, cellWidth = 10, cellHeight = 10;
    private boolean proporcionalCells = true;
    private Environment environment = null;
    private ImageCache imageCache = ImageCache.getInstance();
    private DisplayImage[][] imageLocations;
    private Color highlightedCellBorderColor = new Color(255, 0, 0),
            highlightedSightBorderColor = new Color(0, 255, 0),
            highlightedOlfactoryBorderColor = new Color(0, 0, 255);
    private Represetative2dObject selectedObjectFollow = null;
    private ObservableProperty observable = new ObservableProperty();

    /** Creates new form MapaView */
    public MapaView() {
        initComponents();
        environment = Environment.getInstance();
        environment.addObserver(this);

        size = environment.getSize();
        //System.out.println("tamanho do mapa: " + size);
        preloadImages();
        createImageLocations();
    }

    /** This method is called from within the constructor to
     * initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is
     * always regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        setBackground(new java.awt.Color(229, 240, 226));
        setBorder(javax.swing.BorderFactory.createLineBorder(new java.awt.Color(0, 0, 0)));

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 398, Short.MAX_VALUE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 298, Short.MAX_VALUE)
        );
    }// </editor-fold>//GEN-END:initComponents

    // Variables declaration - do not modify//GEN-BEGIN:variables
    // End of variables declaration//GEN-END:variables
    /**
     * Preload every image that will be used, for a better performance
     */
    private void preloadImages() {
        try {
            imageCache.put(DisplayImage.getFullPathName("background.png"));
            imageCache.put(DisplayImage.getFullPathName("food_0.png"));
            imageCache.put(DisplayImage.getFullPathName("food_1.png"));
            imageCache.put(DisplayImage.getFullPathName("unknow.png"));
        } catch (IOException ex) {
            System.out.println(DisplayImage.getFullPathName("background.png"));
            System.out.println("falha ao pre-carregar imagens");
            System.out.println(ex.getMessage());
            ex.printStackTrace();
        }
    }

    /**
     * Creates a label for every position in map. Stores then in a matrix
     */
    private void createImageLocations() {
        imageLocations = new DisplayImage[size][size];

        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                DisplayImage dispImg = new DisplayImage(i, j);
                imageLocations[i][j] = dispImg;
                add(dispImg);

                dispImg.addMouseListener(new java.awt.event.MouseAdapter() {

                    public void mouseClicked(java.awt.event.MouseEvent evt) {
                        imageLocationMouseClicked(evt);
                    }
                });
            }
        }
    }

    /**
     * Handles painting of this panel
     * @param g Graphics component to draw to
     */
    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        drawMap(g2);
        //System.out.println("larg cel: " + cellHeight);
    }

    /**
     * Excutes some taks needed before drawing
     */
    private void preDrawingOperations() {
        calculateImageLocationSpaces();
    }

    /**
     * Calculates the spaces in each location of the chessboard in order to meet
     * the currnet screen size
     *
     */
    private void calculateImageLocationSpaces() {
        if (size == 0) {
            size = 10;
        }
        if (proporcionalCells) {
            cellHeight = Math.min(getWidth(), getHeight()) / size;
            cellWidth = Math.min(getWidth(), getHeight()) / size;
        } else {
            cellHeight = getWidth() / size;
            cellWidth = getHeight() / size;
        }
    }

    /**
     * draws field lines creating locations
     * @param graphics2d
     */
    private void drawMap(Graphics2D graphics2d) {
        preDrawingOperations();

        for (int i = 0; i < size; i++) {
            for (int j = 0; j < size; j++) {
                drawLocation(graphics2d, i, j);
            }
        }
        if (selectedObjectFollow != null) {
            drawHighlightedBorder(graphics2d);
            updateSelectedIndividualPanel();
        }
    }

    /**
     * Draws one single point location
     * @param graphics2d
     * @param indiceHorizontal
     * @param indiceVertical
     */
    private void drawLocation(Graphics2D graphics2d, int indiceHorizontal, int indiceVertical) {
        //System.out.println("draw location " + indiceHorizontal + ":" + indiceVertical);
        int posicaoHorizontal = indiceVertical * cellWidth;
        int posicaoVertical = indiceHorizontal * cellHeight;

        drawLocationBackgroundLines(graphics2d, posicaoHorizontal, posicaoVertical, indiceHorizontal, indiceVertical);
        adjustImageLocationPosition(indiceHorizontal, indiceVertical, posicaoHorizontal, posicaoVertical);
        Represetative2dObject repObj = findRepresentativeObject(indiceHorizontal, indiceVertical);
        if (repObj != null) {
            //System.out.println("tem um objeto na posicao: " + indiceHorizontal + " " + indiceVertical);
            loadImageLocationImage(indiceHorizontal, indiceVertical, repObj);
        } else {
            loadImageLocationEmpty(indiceHorizontal, indiceVertical);
        }
    }

    /**
     * Draws the lines in background
     * @param graphics2d
     * @param posicaoHorizontal
     * @param posicaoVertical
     * @param larguraCelula
     * @param alturaCelula
     */
    private void drawLocationBackgroundLines(Graphics2D graphics2d, int posicaoHorizontal, int posicaoVertical, int indiceHorizontal, int indiceVertical) {
        graphics2d.drawRect(posicaoVertical, posicaoHorizontal, cellWidth, cellHeight);
    }

    /**
     * Searches in environment, at specific place, for the first Representative2dObject
     * @param indiceHorizontal
     * @param indiceVertical
     * @return
     */
    private Represetative2dObject findRepresentativeObject(int indiceHorizontal, int indiceVertical) {
        return environment.getFirstIndividualOrObjectInPosition(indiceHorizontal, indiceVertical);
    }

    /**
     * Moves the ImageLocations to the correct position and resize them to the
     * correct size
     * @param indiceHorizontal
     * @param indiceVertical
     * @param posicaoHorizontal
     * @param posicaoVertical
     */
    private void adjustImageLocationPosition(int indiceHorizontal, int indiceVertical, int posicaoHorizontal, int posicaoVertical) {
        //System.out.println("movendo a label " + indiceHorizontal + ":" + indiceVertical + " para a posicao " + posicaoHorizontal + ":" + posicaoVertical);
        imageLocations[indiceHorizontal][indiceVertical].setLocation(posicaoHorizontal, posicaoVertical);
        imageLocations[indiceHorizontal][indiceVertical].setSize(cellWidth, cellHeight);
        imageLocations[indiceHorizontal][indiceVertical].removeImage();
        //imageLocations[indiceHorizontal][indiceVertical].setImage(imageCache.getResized(DisplayImage.getFullPathName("background.png"), cellWidth - 1, cellHeight - 1));
        imageLocations[indiceHorizontal][indiceVertical].setText(indiceHorizontal + ":" + indiceVertical);
    }

    /**
     * Loads an image from cache to an ImageLocation
     *
     * @param indiceHorizontal
     * @param indiceVertical
     * @param repObj
     */
    private void loadImageLocationImage(int indiceHorizontal, int indiceVertical, Represetative2dObject repObj) {
        imageLocations[indiceHorizontal][indiceVertical].setImage(
                imageCache.getResized(
                DisplayImage.getFullPathName(repObj.getImageName()), cellWidth - 1, cellHeight - 1));
        //System.out.println("adicionada a imagem " + DisplayImage.getFullPathName(repObj.getImageName()) + " na posicao " + indiceHorizontal + ":" + indiceVertical);
    }

    /**
     * Loads an image from cache to an ImageLocation
     *
     * @param indiceHorizontal
     * @param indiceVertical
     * @param repObj
     */
    private void loadImageLocationEmpty(int indiceHorizontal, int indiceVertical) {
        imageLocations[indiceHorizontal][indiceVertical].removeImage();
    }

    /**
     * Draws a highlighted border around the last selected cell
     * @todo make the olfactory and visual range areas filled with alpha
     * @todo problem with ghost images (dead individuals that keep showing)
     * @param graphics2d
     */
    private void drawHighlightedBorder(Graphics2D graphics2d) {
        if (selectedObjectFollow != null) {
            int x = (int) selectedObjectFollow.getX();
            int y = (int) selectedObjectFollow.getY();
            //System.out.println("há um selecionado no indice " + x + ":" + y);

            if (Individual.class.isInstance(selectedObjectFollow)) {
                Individual ind = (Individual) selectedObjectFollow;
                if (ind.isDead()) {
                    ind = null;
                    return;
                } else {
                    drawBlockSquare(graphics2d, x, y, ind.getSightRange(), highlightedSightBorderColor);
                    drawBlockSquare(graphics2d, x, y, ind.getOlfactoryRange(), highlightedOlfactoryBorderColor);
                }
            }
            // the selected cell
            drawBlockSquare(graphics2d, x, y, 0, highlightedCellBorderColor);
        }
    }

    /**
     * Draws a square based on blocks values, instead coordinates.
     * @param graphics2d the Graphics2d where to draw to
     * @param horizontalIndex the horizontal centre position in number of blocks
     * @param verticalIndex the vertical centre position in number of block
     * @param radius the radius in number of blocks. 0 means juste the central block.
     */
    private void drawBlockSquare(Graphics2D graphics2d, int horizontalIndex, int verticalIndex, int radius) {
        int x0 = horizontalIndex - radius;
        int y0 = verticalIndex - radius;
        int x1 = 2 * radius + 1;
        int y1 = 2 * radius + 1;
        //System.out.println("square " + x0 + ":" + y0 + " - " + x1 + ":" + y1);
        graphics2d.drawRect(y0 * cellHeight, x0 * cellWidth, y1 * cellHeight, x1 * cellWidth);
    }

    /**
     * Draws a square based on blocks values, instead coordinates.
     * @param graphics2d the Graphics2d where to draw to
     * @param horizontalIndex the horizontal centre position in number of blocks
     * @param verticalIndex the vertical centre position in number of block
     * @param radius the radius in number of blocks. 0 means juste the central block.
     * @param color the Color to be used in drawing
     */
    private void drawBlockSquare(Graphics2D graphics2d, int horizontalIndex, int verticalIndex, int radius, Color color) {
        graphics2d.setColor(color);
        drawBlockSquare(graphics2d, horizontalIndex, verticalIndex, radius);
    }

    /**
     * Handles the mouseClicked events for the image locations
     * @param evt
     */
    private void imageLocationMouseClicked(MouseEvent evt) {
        DisplayImage dispImg = (DisplayImage) evt.getSource();
        //System.out.println("clicou na casa: " + dispImg.getHorizontalPosition() + ":" + dispImg.getVerticalPosition());
        Represetative2dObject repObj = findRepresentativeObject(dispImg.getHorizontalPosition(), dispImg.getVerticalPosition());
        if (repObj != null) {
            //System.out.println("Aqui tem: " + repObj);
            selectedObjectFollow = repObj;
        }
        paint(getGraphics());
    }

    /**
     * This method receives signals from any Observable that is being watched
     * @param o
     * @param arg
     */
    public void update(Observable o, Object arg) {
        //System.out.println("update mapView");
        repaint();
    }

    private void updateSelectedIndividualPanel() {
        observable.setChanged();
        observable.notifyObservers(selectedObjectFollow);
    }

    /**
     * Adds and Observer to this instance
     * @param o
     */
    public void addObserver(Observer o) {
        observable.addObserver(o);
    }
}
